#pragma once

// CDbReader - EGTB Reader Class supporting multi-threaded access
//////////////////////////////////////////////////////////////////////////////////////////

#include "Terminate.h"
#include "Progress.h"

namespace DbReader
{
	#include "Index.h"

	// Constructor
	/////////////////////////////////////////////////////
	template<int MaxPieces, int MaxPiece, int MaxRank>
	CDbReader<MaxPieces, MaxPiece, MaxRank>::CDbReader()
	{
		// zero variables
		m_szReaderDatabaseDir = NULL;
		m_pWDLManager = NULL;
		m_pDTMManager = NULL;
		m_nCompresssionType = CPR_UNKNOWN;
		m_eDraughtsType = eCheckersType::UnknownType;
		m_pWDLGlobalBlockNumber = NULL;
		m_pDTMGlobalBlockNumber = NULL;
		m_nMaxDetectedPieces = 0;

		// clear files
		memset(m_dbfp, 0, MaxPieces * sizeof(FILE*));
		memset(m_dtmdbfp, 0, MaxPieces * sizeof(FILE*));

		// clear sub-databases info
		memset(wdlsubdatabase, 0, (MaxPiece+1)*(MaxPiece+1)*(MaxPiece+1)*(MaxPiece+1)*MaxRank*MaxRank*2*sizeof(cprsubdb));
		memset(dtmsubdatabase, 0, (MaxPiece+1)*(MaxPiece+1)*(MaxPiece+1)*(MaxPiece+1)*MaxRank*MaxRank*2*sizeof(cprsubdb ));
	}


	// Destructor
	/////////////////////////////////////////////////////////
	template<int MaxPieces, int MaxPiece, int MaxRank>
	CDbReader<MaxPieces, MaxPiece, MaxRank>::~CDbReader()
	{
		// free global block number arrays
		if(m_pWDLGlobalBlockNumber != NULL)
		{
			delete[] m_pWDLGlobalBlockNumber;
			m_pWDLGlobalBlockNumber = NULL;
		}
		if(m_pDTMGlobalBlockNumber != NULL)
		{
			delete[] m_pDTMGlobalBlockNumber;
			m_pDTMGlobalBlockNumber = NULL;
		}

		// free paging managers
		if(m_pWDLManager != NULL)
		{
			delete m_pWDLManager;
			m_pWDLManager = NULL;
		}
		if(m_pDTMManager != NULL)
		{
			delete m_pDTMManager;
			m_pDTMManager = NULL;
		}

		// close open database files
		for(int i=0;i<MaxPieces;i++)
		{
			if(m_dbfp[i] != NULL)		// WDL file
				fclose(m_dbfp[i]);

			if(m_dtmdbfp[i] != NULL)	// DTM file
				fclose(m_dtmdbfp[i]);
		}

		// free database directory
		if(m_szReaderDatabaseDir != NULL)
			delete m_szReaderDatabaseDir;
	}

	 // Public interface
	///////////////////////////////////////////////////////////////////////////////////


	// public API method for initializing the EGTB database
	//////////////////////////////////////////////////////////////////////////////////////
	template<int MaxPieces, int MaxPiece, int MaxRank>
	int CDbReader<MaxPieces, MaxPiece, MaxRank>::Initialize(int suggestedMB, SEngineData* ed, 
									const wchar_t* lpDbDir)
	{
		// variables
		FILE *fp;
		wchar_t dbname[MAX_PATH];
		int i,n;
		int fpcount = 0;
		int pieces=0;

		// set EGTB directory
		SetDatabaseDirectory(lpDbDir);

		// init supporting engine variables
		InitBool();

		// detect largest present database and put the number of pieces in variable pieces:
		pieces = DetectLargestDB();

		// if no DB found, exit
		if(pieces < 2)
			return  -1;

		m_nCacheSizeMB = suggestedMB; 

		// open file pointers for each db: keep open all the time.
		fpcount=0;
		for(n=2; n<=pieces; n++)
		{
			SetReaderDatabaseName(dbname, n, false, false);
			_wfopen_s(&(m_dbfp[fpcount]), dbname, L"rb");


			SetReaderDatabaseName(dbname, n, false, true);
			_wfopen_s(&(m_dtmdbfp[fpcount]), dbname, L"rb");

			fpcount++;
		}

		// parse WDL files
		if(ParseSetOfEGTBFiles(fpcount, &m_dbfp[0], &wdlsubdatabase, &m_pWDLManager, &m_pWDLGlobalBlockNumber) == false)
			return -1;

		// parse DTM files
		if(ParseSetOfEGTBFiles(fpcount, &m_dtmdbfp[0], &dtmsubdatabase, &m_pDTMManager, &m_pDTMGlobalBlockNumber) == false)
			return -1;

		m_nMaxDetectedPieces = pieces;

		// Preload EGTB database
		PreloadDatabase(ed, m_pWDLManager->GetTotalBlockCount(), m_pDTMManager->GetTotalBlockCount());
		
		return pieces;
	}


	// Looks up the position in the EGTB databases
	////////////////////////////////////////////////////////////////
	template<int MaxPieces, int MaxPiece, int MaxRank>
	template<typename BB, int BitsPerRow, int MaxField>
	int CDbReader<MaxPieces, MaxPiece, MaxRank>::Lookup(BB *q, int cl, uint8 *pDTM)
	{
		// variables
		int64 index;
		int bm, bk, wm, wk, bmrank=0, wmrank=0;
		*pDTM = 0;
	
		int i;
		int64 blocknumberWDL;	
		int64 blocknumberDTM;
		int reverse = 0;
		int n;
		BB revpos;
		BB p;
		cprsubdb *wdlpointer;
		cprsubdb *dtmpointer;
		uint8 byte;

		// copy color
		int color = cl;

		// copy the bitboard
		p=*q;
		
		// calculate number of pieces
		wm = BitCountXX<BB>(p.wm);
		wk = BitCountXX<BB>(p.wk);
		bm = BitCountXX<BB>(p.bm);
		bk = BitCountXX<BB>(p.bk);
		int wCount = wm + wk;
		int bCount = bm + bk;

		// over the db pieces counts?
		if( (bCount + wCount > MaxPieces) || (bCount>MaxPiece) || (wCount>MaxPiece))
			return UNKNOWN;	

		// check loss or win position
		if(wCount == 0)
		{
			*pDTM = 0;
			if(color == WHITE)
				return LOSS;
			else
				return WIN;
		}
		if(bCount == 0)
		{
			*pDTM = 0;
			if(color == WHITE)
				return WIN;
			else
				return LOSS;
		}

	
		// decide maximum rank of men
		if(wm)
			wmrank = MSBxx<BB>(p.wm)/BitsPerRow;
		if(bm)
			bmrank = (MaxField-LSBxx<BB>(p.bm))/BitsPerRow;	
	
		// if this BitBoard is dominated by the "wrong" side, we have to
		// reverse it!
		if (( ((bm+bk-wm-wk)<<16) + ((bk-wk)<<8) + ((bmrank-wmrank)<<4) + (1-cl)) > 0)
			reverse = 1;
	
		// if necessary, reverse the board
		if (reverse)
		{
			// white is dominating this BitBoard, change colors
			revpos.wm = RevertXX<BB>(p.bm);
			revpos.wk = RevertXX<BB>(p.bk);
			revpos.bm = RevertXX<BB>(p.wm);
			revpos.bk = RevertXX<BB>(p.wk);
			color = color^1;
			p = revpos;
		
			reverse = bm;
			bm = wm;
			wm = reverse;
		
			reverse = bk;
			bk = wk;
			wk = reverse;
		
			reverse = bmrank;
			bmrank = wmrank;
			wmrank = reverse;
		
			reverse = 1;
		} // if


		// get pointer to db
		wdlpointer = &wdlsubdatabase[wm][wk][bm][bk][wmrank][bmrank][color];
		dtmpointer = &dtmsubdatabase[wm][wk][bm][bk][wmrank][bmrank][color];
	
		// if databases do not exist, return UNKNOWN
		if(wdlpointer->ispresent == 0 || (m_dbfp[wdlpointer->fp] == NULL))
			return UNKNOWN;
		if(dtmpointer->ispresent == 0 || (m_dtmdbfp[dtmpointer->fp] == NULL))
			return UNKNOWN;

		// calculate index
		BitBoardToIndexXX<BB>(&p, &index);
		
		// calculate block numbers for WDL and DTM
		//////////////////////////////////////////////
		
		// WDL
		// local block number in the subdatabase
		blocknumberWDL = ((index / 4 + wdlpointer->startbyte) / m_pWDLManager->GetBlockSize());
		// make semi-global blocknumber in the file
		blocknumberWDL += wdlpointer->firstblockindb;
		// make global blocknumber over the all open files
		blocknumberWDL += m_pWDLGlobalBlockNumber[wdlpointer->fp];

		// DTM
		// local block number in the subdatabase
		blocknumberDTM = ((index + dtmpointer->startbyte) / m_pDTMManager->GetBlockSize());
		// make semi-global blocknumber in the file
		blocknumberDTM += dtmpointer->firstblockindb;
		// make global blocknumber over the all open files
		blocknumberDTM += m_pDTMGlobalBlockNumber[dtmpointer->fp];

		// get the uncompressed blocks
		uint8* pWDLBlock = m_pWDLManager->GetBlock(wdlpointer->fp, blocknumberWDL);
		uint8* pDTMBlock = m_pDTMManager->GetBlock(dtmpointer->fp, blocknumberDTM);

		// if blocks are empty, possible error, return unknown
		if(pWDLBlock == NULL || pDTMBlock == NULL)
		{
			assert(false);
			return UNKNOWN;
		}

		// calculate indexes in the block
		int32 indexInBlockWDL = ((index / 4 + wdlpointer->startbyte) % m_pWDLManager->GetBlockSize());
		int32 indexInBlocDTM = ((index + dtmpointer->startbyte) % m_pDTMManager->GetBlockSize());

		// return values
		*pDTM = pDTMBlock[indexInBlocDTM]; // set DTM

		// extract WDL from 2-bit record of four values in one byte
		byte = pWDLBlock[indexInBlockWDL];
		byte = (byte >> (2*(index%4))) & 3;
		return byte;
	}


	 // Private helper methods
	///////////////////////////////////////////////////////////////////////////////////

	
	// set the DB directory
	///////////////////////////////////////////////////
	template<int MaxPieces, int MaxPiece, int MaxRank>
	void CDbReader<MaxPieces, MaxPiece, MaxRank>::SetDatabaseDirectory(const wchar_t* lpDir)
	{
		int len = wcslen(lpDir);
		m_szReaderDatabaseDir = new wchar_t[len + 1];
		wcscpy_s(m_szReaderDatabaseDir, len + 1, lpDir);
	}


	// returns the largest DB found
	//////////////////////////////////////////////////////
	template<int MaxPieces, int MaxPiece, int MaxRank>
	int CDbReader<MaxPieces, MaxPiece, MaxRank>::DetectLargestDB()
	{
		// variables
		FILE *fp;
		wchar_t dbname[MAX_PATH];
		int n, pieces = 0;

		for(n=2; n<=MaxPieces; n++)
		{
			// check WDL database
			SetReaderDatabaseName(dbname, n, false, false);
			_wfopen_s(&fp, dbname, L"rb");
			if(fp)
			{
				fclose(fp);

				// check DTM database
				SetReaderDatabaseName(dbname, n, false, true);
				_wfopen_s(&fp, dbname, L"rb");
				if(fp)
				{
					fclose(fp);
					pieces=n;
				}
				else
					break;
			}
			else
				break;
		}

		return pieces;
	}


	// parses the set of EGTB files of one type, either WDL or DTM
	//////////////////////////////////////////////////////////////////////
	template<int MaxPieces, int MaxPiece, int MaxRank>
	bool CDbReader<MaxPieces, MaxPiece, MaxRank>::ParseSetOfEGTBFiles(int nFpCount, FILE** files, 
					cprsubdb (*pSubDb)[MaxPiece+1][MaxPiece+1][MaxPiece+1][MaxPiece+1][MaxRank][MaxRank][2], 
								PagingManager** ppManager, int64** ppGlobalBlockNumbers)
	{
		EGTBFile* pFiles = new EGTBFile[nFpCount];
		memset(pFiles, 0, sizeof(EGTBFile) * nFpCount);

		// iterate through all files
		for(int i = 0; i < nFpCount; i++)
		{
			int64 nPagingDataOffset = 0;

			// try to parse the file
			if(ParseEGTBFile(files[i], i, pSubDb, &nPagingDataOffset) == false)
			{
				// if loading failed
				delete[] pFiles;
				return false;
			}

			// create record for the PagingManager
			pFiles[i].fp = files[i];
			pFiles[i].nPageDataOffset = nPagingDataOffset;
		} // for

		// try to init paging manager
		PagingManager* pManager = new PagingManager(m_nCacheSizeMB / 2);
		if(pManager->Initialize(nFpCount, pFiles, m_nCompresssionType) == false)
		{
			delete[] pFiles;
			delete pManager;
			return false;

		}
		*ppManager = pManager;

		// set the global block number value array
		*ppGlobalBlockNumbers = new int64[nFpCount];
		for(int i = 0; i < nFpCount; i++)
		{
			(*ppGlobalBlockNumbers)[i] = pFiles[i].nGlobalBlockNumber;
		}

		// clean up and exit
		delete[] pFiles;

		return true;
	}


	// parses one single file
	// TO-DO: describe error messages
	//////////////////////////////////////////////////////////
	template<int MaxPieces, int MaxPiece, int MaxRank>
	bool CDbReader<MaxPieces, MaxPiece, MaxRank>::ParseEGTBFile(FILE* fp, int fileIndex, 
					cprsubdb (*pSubDb)[MaxPiece+1][MaxPiece+1][MaxPiece+1][MaxPiece+1][MaxRank][MaxRank][2], 
																int64 *pPagingDataOffset)
	{
		// read the main header
		if(_fseeki64(fp, 0, SEEK_SET))
			return false;
		EGTBMainHeader header;
		memset(&header, 0, sizeof(EGTBMainHeader));
		fread(&header, (size_t)1, sizeof(EGTBMainHeader), fp);
		
		// check the EGTB header tag
		if(strncmp(&header.szHeaderTag[0], szDBHeaderTag, EGTB_HEADER_TAG_SIZE) != 0)
			return false;

		// check compression type
		if(m_nCompresssionType == CPR_UNKNOWN)
		{
			m_nCompresssionType = header.nCompressionType;		
		}
		else if(m_nCompresssionType != header.nCompressionType)
		{
			assert(false);
			return false;
		}

		// check the draughts type
		eCheckersType draughtsType = PDNCodeToEnum(header.nDraughtsType);
		if(m_eDraughtsType == eCheckersType::UnknownType)
		{
			m_eDraughtsType = draughtsType;
		}
		else if(m_eDraughtsType != draughtsType)
		{
			assert(false);
			return false;
		}

		*pPagingDataOffset = header.nPagingDataOffset;

		// iterate among all subdatabases
		int subDbCount = header.nSubDbCount;
		for(int i = 0; i < subDbCount; i++)
		{
			// clear subdb structure
			EGTBSubDatabase subdb;
			memset(&subdb, 0, sizeof(EGTBSubDatabase));

			// read it
			fread(&subdb, (size_t)1, sizeof(EGTBSubDatabase), fp);

			// fill in subdatabase record
			cprsubdb* pDb = &((*pSubDb)[subdb.wm][subdb.wk][subdb.bm][subdb.bk][subdb.wmrank][subdb.bmrank][subdb.color]);
			pDb->ispresent = 1;
			pDb->fp = fileIndex;
			pDb->firstblockindb = subdb.nStartBlock;
			pDb->startbyte = subdb.nStartByteInBlock;
		} // for		

		return true;
	}

	// Preloads the database to the caches
	////////////////////////////////////////////////////
	template<int MaxPieces, int MaxPiece, int MaxRank>
	void CDbReader<MaxPieces, MaxPiece, MaxRank>::PreloadDatabase(SEngineData* ed, int iNumberOfBlocksWDL, int iNumberOfBlocksDTM)
	{
		// calculate progress variables
		int iWDLProgress = min(m_pWDLManager->GetCacheBlockCount(), iNumberOfBlocksWDL);
		int iDTMProgress = min(m_pDTMManager->GetCacheBlockCount(), iNumberOfBlocksDTM);

		int maxprogress = iWDLProgress + iDTMProgress;

		ENGINE_PROGRESS_DECLARE();
		ENGINE_PROGRESS_INIT(3, maxprogress);
		int progress = 0;

		
		// PRELOADING WDL CACHE
		//************************************************************
		int fp = 0;
		for(int i=0; i<iWDLProgress; i++)
		{
			if(fp < m_nMaxDetectedPieces - 2)
			{
				if(i >= m_pWDLGlobalBlockNumber[fp + 1])
					fp++;
			}

			m_pWDLManager->GetBlock(fp, i);

			// report progress
			progress++;
			ENGINE_PROGRESS_REPORT(progress, ed);

			// check the termination
			if( (progress & 0x3FF) == 0 )
				CHECK_TERMINATE(ed);

		} // for
			
		// PRELOADING DTM CACHE
		//************************************************************
		fp = 0;
		for(int i=0; i<iDTMProgress; i++)
		{
			if(fp < m_nMaxDetectedPieces - 2)
			{
				if(i >= m_pDTMGlobalBlockNumber[fp + 1])
					fp++;
			}

			m_pDTMManager->GetBlock(fp, i);
			
			// report progress
			progress++;
			ENGINE_PROGRESS_REPORT(progress, ed);

			// check the termination
			if( (progress & 0x3FF) == 0 )
				CHECK_TERMINATE(ed);
		} // for
	}

	// creates a database-file name
	// either DTM file or index file od DB file
	////////////////////////////////////////////////////////////
	template<int MaxPieces, int MaxPiece, int MaxRank>
	void CDbReader<MaxPieces, MaxPiece, MaxRank>::SetReaderDatabaseName(wchar_t *dbname, int numPieces, bool index, bool dtm)
	{
		wcscpy_s(dbname, MAX_PATH, m_szReaderDatabaseDir);
		int len = (int)wcslen(dbname);
		if(dbname[len - 1] != L'\\')
		{
			wcscat_s(dbname, MAX_PATH, L"\\");
		}

		wchar_t szFileName[MAX_PATH];

		if(dtm == true)
		{
			if(index == true)
				swprintf_s(szFileName, MAX_PATH,L"db%i.dtm.idx", numPieces);
			else
				swprintf_s(szFileName, MAX_PATH, L"db%i.dtm.cpr", numPieces);
		}
		else
		{
			if(index == true)
				swprintf_s(szFileName, MAX_PATH,L"db%i.idx", numPieces);
			else
				swprintf_s(szFileName, MAX_PATH, L"db%i.cpr", numPieces);
		}

		wcscat_s(dbname, MAX_PATH, szFileName);
	}
}
